package com.devunion.bogdan.view.project;

import com.devunion.bogdan.model.project.Project;
import com.devunion.bogdan.model.project.Paper;
import com.devunion.bogdan.model.project.PaperType;
import com.devunion.bogdan.model.event.PaperListener;
import com.devunion.bogdan.model.event.PaperEvent;
import com.devunion.bogdan.model.event.PageListener;
import com.devunion.bogdan.model.event.PageEvent;
import com.devunion.bogdan.util.IconHelper;

import javax.swing.*;
import javax.swing.tree.*;
import java.util.Map;
import java.util.HashMap;
import java.awt.*;

/**
 * @author Viktar Liaskovich
 */
public class DefaultProjectTree extends JTree {
    private Map<Paper, AbstractEntryNode> model2View = new HashMap<Paper, AbstractEntryNode>();
    private ProjectRootNode root;

    public DefaultProjectTree(Project project) {
        if (project == null) {
            throw new IllegalArgumentException("Project can't be null");
        }

        project.addPaperListener(new ProjectHolderEntryListener());

        root = new ProjectRootNode(project);

//        model2View.put(project.getProjectRoot(), root);

        setModel(new DefaultTreeModel(root));

        setIcons();
    }

    private void setIcons() {
        DefaultTreeCellRenderer newCellRenderer = new ProjectTreeCellRenderer();

        setCellRenderer(newCellRenderer);
    }

    private void addTreeNode(Paper entry) {

        AbstractEntryNode entryNode = new PaperNode(entry);

        model2View.put(entry, entryNode);

        entry.addPaperListener(new TreePageListener(entryNode));

        root.add(entryNode);

        ((DefaultTreeModel) treeModel).reload(root);

        setSelectionPath(new TreePath(entryNode.getPath()));
    }

    private void removeTreeNode(Paper entry) {
        root.remove(model2View.get(entry));

        model2View.remove(entry);

        ((DefaultTreeModel) treeModel).reload(root);
    }

    private class ProjectHolderEntryListener implements PaperListener {
        public void paperAdded(PaperEvent e) {
            addTreeNode(e.getEntry());
        }

        public void paperRemoved(PaperEvent e) {
            removeTreeNode(e.getEntry());
        }
    }

    private static class ProjectTreeCellRenderer extends DefaultTreeCellRenderer {
        private static Icon bookIcon = IconHelper.getIcon("Book16.gif");
        private static Icon articleIcon = IconHelper.getIcon("Article16.gif");
        private static Icon letterIcon = IconHelper.getIcon("Letter16.gif");
        private static Icon documentIcon = IconHelper.getIcon("Document16.gif");
        private static Icon otherIcon = IconHelper.getIcon("UndefinedDocument16.gif");

        private static Icon pageIcon = IconHelper.getIcon("Page16.gif");

        public ProjectTreeCellRenderer() {

            setLeafIcon(pageIcon);
            setOpenIcon(bookIcon);
            setClosedIcon(bookIcon);
        }

        public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
            Component cellRendererComponent = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);

            if (value instanceof PaperNode) {
                Paper projectEntry = ((PaperNode) value).getUserObject();

                PaperType paperType = projectEntry.getInfo().getType();

                if (paperType == PaperType.BOOK) {
                    setIcon(expanded ? openIcon : closedIcon);
                } else if (paperType == PaperType.LETTER) {
                    setIcon(letterIcon);
                } else if (paperType == PaperType.ARTICLE) {
                    setIcon(articleIcon);
                } else if (paperType == PaperType.DOCUMENT) {
                    setIcon(documentIcon);
                } else if (paperType == PaperType.OTHER) {
                    setIcon(otherIcon);
                } else {
                    setIcon(expanded ? openIcon : closedIcon);
                }
            } else if (value instanceof ProjectRootNode) {
                setIcon(IconHelper.getIcon("ProjectRoot16.gif"));
            } else { // PageNode
                setIcon(leafIcon);
            }

            return cellRendererComponent;
        }
    }

    private class TreePageListener implements PageListener {
        private final AbstractEntryNode entryNode;

        public TreePageListener(AbstractEntryNode entryNode) {
            this.entryNode = entryNode;
        }

        public void pageAdded(PageEvent e) {
            AbstractEntryNode pageNode = new PageNode(e.getPage());

            entryNode.add(pageNode);

            ((DefaultTreeModel) treeModel).reload(entryNode);

            setSelectionPath(new TreePath(pageNode.getPath()));
        }

        public void pageRemoved(PageEvent e) {
            PageNode pageNode = (PageNode) DefaultProjectTree.this.getLastSelectedPathComponent();
            
            //get the sibling node to be selected after removing the
            //selected node
            MutableTreeNode toBeSelNode = getSibling(pageNode);

            //if there are no siblings select the parent node after removing the node
            if (toBeSelNode == null) {
                toBeSelNode = entryNode;
            }

            entryNode.remove(pageNode);

            ((DefaultTreeModel) treeModel).reload(entryNode);

            //make the node visible by scroll to it
            TreeNode[] nodes = ((DefaultTreeModel) treeModel).getPathToRoot(toBeSelNode);
            TreePath path = new TreePath(nodes);
            DefaultProjectTree.this.scrollPathToVisible(path);
            DefaultProjectTree.this.setSelectionPath(path);
        }


        /**
         * This method returns the previous sibling node
         * if there is no previous sibling it returns the next sibling
         * if there are no siblings it returns null
         *
         * @param selNode selected node
         * @return previous or next sibling, or parent if no sibling
         */
        private MutableTreeNode getSibling(DefaultMutableTreeNode selNode) {
            //get previous sibling
            MutableTreeNode sibling = selNode.getPreviousSibling();

            if (sibling == null) {
                //if previous sibling is null, get the next sibling
                sibling = selNode.getNextSibling();
            }

            return sibling;
        }
    }
}
